<?php 
/**
 * Class description
 *
 * @since 8 мая 2010
 * @author diotheos
 */
class Lib_Integrator_Site_Ria_Common_Base implements Lib_Integrator_Site_Ria_Common_IIntegrate {
	// Integration modes	
	/**
	 * @deprecated
	 */
	const MODE_ONLY_UPDATE         = 'MODE_ONLY_UPDATE';
	const MODE_ONLY_ADD_NEW        = 'MODE_ONLY_ADD_NEW'; // by default
	const MODE_ADD_NEW_AND_UPDATE  = 'MODE_ADD_NEW_AND_UPDATE'; // MOSTLY clever param

	// Restricion modes
	const REST_GT_SPECIFIED_ID         = 11;
	const REST_LT_SPECIFIED_ID         = 12;
	const REST_EQ_SPECIFIED_ID         = 13;
	const REST_BETWEEN_SPECIFIED_IDS   = 14;
	const REST_LAST_ID                 = 15; // by default
	
	// Removing modes
	const REMOVE_DESTINATION   = 21;
	const REMOVE_INTEGRATION   = 22;
	const REMOVE_BOTH          = 23;
	const REMOVE_NONE          = 24; // by default
	
	
    public $source = '';
    public $integration = '';
    public $destination = '';
    
    public $d = 'id';
    public $s2i = false;
    
    public $integrationMode = self::MODE_ONLY_ADD_NEW;   
    public $restrictionMode = self::REST_LAST_ID;
    public $removeMode      = self::REMOVE_NONE;
    public $mode = 'MODE_ONLY_ADD_NEW';
    
    public $sourceSelect = '*';
    public $sourceFilter = '1';
    public $sourceLimit = 0;
    public $sourceGroupBy = '';
    public $sourceOrderBy = '';
    
    private $_connector;
    private $_config;
    private $_section;
    
    /**
     * Function description
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @param Lib_Integrator_Site_Ria_Connector $connector
     * @param array [optional] $options
     * @return void
     */
    public function __construct(Lib_Integrator_Site_Ria_Connector $connector, $options, $section = null, $additionalOptions = array()){
    	$this->setConnector($connector);
    	$this->_config = $options;
    	if(null !== $section){
    		$this->_section = $section;
    		$currentOptions = $this->_config->$section->toArray();
    	} else {
    		$currentOptions = array();
    	}
    	
    	
    	
    	if($currentOptions){
    		$this->setParams($currentOptions);
    	}
    	if(count($additionalOptions)){
    		$this->setParams($additionalOptions);
    	}
    	if(null !== $section){
    		$this->integrationMode = self::$this->mode;
	        $this->integration = ($this->integration)?$this->integration:"Integrate".$this->destination;
	        $this->s2i = $this->s2i?$this->s2i:'old_'.$this->d2i;
    	}
    }
    /**
     * Function description
     *
     * @since 8 мая 2010
     * @author diotheos
     */
    public function integrate() {
    	
    	$connection = $this->getLocalConnection();
        switch($this->removeMode){
        	case self::REMOVE_BOTH :
        	   $connection->getDbh()->query('TRUNCATE TABLE '.$connection->getTable($this->integration)->getTableName());
        	   $connection->getDbh()->query('TRUNCATE TABLE '.$connection->getTable($this->destination)->getTableName());
            break;
            case self::REMOVE_DESTINATION :
               $connection->getDbh()->query('TRUNCATE TABLE '.$connection->getTable($this->destination)->getTableName());
            break;
            case self::REMOVE_INTEGRATION :
               $connection->getDbh()->query('TRUNCATE TABLE '.$connection->getTable($this->integration)->getTableName());
            break;
            default:
            break;            
        }
        
        switch($this->integrationMode){
            case self::MODE_ONLY_UPDATE:
                $this->onlyUpdate();
            break;
            case self::MODE_ONLY_ADD_NEW:
                $this->onlyAddNew();
            break;
            case self::MODE_ADD_NEW_AND_UPDATE:
                $this->updateAndAddNew();
            break;
        }
    }
    
    /**
     * Function description
     * 
     * @since 10 мая 2010
     * @author diotheos
     * @return void
     */
    public function fallback($limit = 100){
    	$connection = $this->getLocalConnection();
    	$config = $this->getConfigSection($this->_section);

    	$toRemoveIds = $connection->createQuery()
    	   ->select($this->d2i)
    	   ->from($this->integration)
    	   ->limit($limit)
    	   ->execute()->toKeyValueArray($this->d2i,$this->d2i);

    	$toRemoveIds = array_keys($toRemoveIds);

    	$numDeletedIntegrations = $connection->createQuery()
    	   ->delete($this->integration)
    	   ->whereIn($this->d2i,$toRemoveIds)
    	   ->execute();

  
    	$numDeletedRows = $connection->createQuery()
    	   ->delete($this->destination)
    	   ->whereIn($this->d,$toRemoveIds)
    	   ->execute();

    	echo "<div>Was removed <b>$numDeletedIntegrations</b> integrations and <b>$numDeletedRows</b> related rows</div>";
//    	echo "<pre>";
//    		print_r($toRemoveIds);
//    	echo "</pre>";   
////    	$storageTable = new 
//        echo "<pre>";
//        	print_r($config);
//        echo "</pre>";
    }
    /**
     * Function description
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @return void
     */
    public function setConnector(Lib_Integrator_Site_Ria_Connector $connector){
    	$this->_connector = $connector;
    }
    /**
     * Function description
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @return Lib_Integrator_Site_Ria_Connector
     */
    public function getConnector(){
    	return $this->_connector;
    }
    /**
     * Function description
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @return Doctrine_Connection
     */
    public function getConnection(){
    	$connector = $this->getConnector();
    	$connection = $connector->getConnection();
    	return $connection;
    }
    /**
     * Function description
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @return Doctrine_Connection
     */
    public function getLocalConnection(){
    	$connection =  Doctrine_Manager::getInstance()->getConnection('primary');
    	return $connection;
    }
    /**
     * Function description
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @return void
     */
    public function setParams(array $params){
    	foreach($params as $paramName=>$paramValue){
    		$this->setParam($paramName, $paramValue);
    	}
    }
    
    /**
     * Function description
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @return void
     */
    public function setParam($paramName, $paramValue){
        $this->$paramName = $paramValue;
    }
    
    /**
     * Function description
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @return void
     */
    public function getFromSource(){
    	$query = "SELECT ".$this->sourceSelect." FROM ".$this->source;
    	if($this->sourceFilter) $query .=   " WHERE ".$this->sourceFilter;
    	if($this->sourceGroupBy) $query .=   " GROUP BY ".$this->sourceGroupBy;
    	if($this->sourceOrderBy) $query  .= " ORDER BY ".$this->sourceOrderBy;
    	if($this->sourceLimit) $query  .=   " LIMIT ".$this->sourceLimit;
    	
    	$result = $this->getConnection()
    	               ->fetchAll($query);
    	return $result;
    }
    
/**
     * Function description
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @return void
     */
    public function onlyUpdate(){
        echo "<div>Updating existed records</div>";
        
        $existedRecords = $this->getExistedRecords();
        $localTable = $this->destination;
        $count = 0;
        foreach($existedRecords as $existedRecord){
            $record = $this->getLinkedRecord($existedRecord[$this->s]);
            if($record instanceof Doctrine_Record){
                $needSave = false;
                foreach($this->fields as $localField=>$remoteField){
                    if($record->$localField != $existedRecord[$remoteField]){
                        $record->$localField = $existedRecord[$remoteField];
                        $needSave = true;
                    }
                    if($needSave){
                        $record->save();
                        $count++;
                    }
                }
            }
            
        }
        
        echo "<div>Existing <b>($count)</b> records were updated successfully in $localTable. </div>";
    }
    /**
     * Function description
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @return void
     */
    public function onlyAddNew(){
        echo "<div>Adding new records</div>";
        $added = 0;
        $skipped = 0;
        
        
        $localIdentifier  = $this->d;
        $remoteIdentifier = $this->s;
        $localTable = $this->destination;
        $integrateTable = $this->integration;
        $localIntegrationColumn = $this->d2i;
        $remoteIntegrationColumn = $this->s2i;
        
        
        $newRecords = $this->getNewRecords();
        $count = 0;
        $records = array();
        $bridges = array();
        $exRid = array();
        foreach($newRecords as $newRecord){
        	$recordIsGood = true;
            $records[$count] = new stdClass();
            
            foreach($this->fields as $localField=>$remoteField){
            	if(is_array($remoteField)){
            		// Integration is related
            		// retrieving linked section
            		$linkedSection = $remoteField['relate'];
            		$linkedField   = $remoteField['link'];
            		// retrieving linked section config
            		$linkedConfig = $this->getConfigSection($linkedSection);
            		
            		$linkedTable = $this->getLocalConnection()
            		  ->getTable($linkedConfig['integration']);
            		
//            		echo $newRecord[$linkedField];  
            		$linkedSubintegration = $linkedTable->findOneBy($linkedConfig['s2i'],  $newRecord[$linkedField], Doctrine::HYDRATE_ARRAY);
                    if($linkedSubintegration){
                    	// Integration has been founded
                    	$newSubintegrationId = $linkedSubintegration[$linkedConfig['d2i']];
                    	$records[$count]->$localField = $newSubintegrationId;
                    } else {
                    	// Integration hasn't founded
                    	// Removing record from integration stack
                    	$recordIsGood = false;
                    	
                    }
            	} else {
            		// Integration is simple
            		$records[$count]->$localField = $newRecord[$remoteField];
            	}
//                
            }
            if($recordIsGood){
            	$added++;
            	$exRid[$count] = $newRecord[$remoteIdentifier];
                $count++;
            } else {
            	$skipped++;
            	unset($records[$count]);
            }            
        }
        $recordsToSave = array();
        foreach ($records as $record) {
        	$recordToSave = new $localTable();
        	foreach($record as $param=>$value){
        		$recordToSave->$param = $value;
        	}
        	$recordsToSave[] = $recordToSave;
        }

        $this->getLocalConnection()->flush();
        $count = 0;
        foreach($recordsToSave as $recordToSave){
        	$bridge = new $integrateTable();
        	$bridge->$localIntegrationColumn = $recordToSave->$localIdentifier;
        	$bridge->$remoteIntegrationColumn = $exRid[$count];
        	$count++;
        }
        $this->getLocalConnection()->flush();
        echo "
        <div>New <b>($added)</b> records were added successfully to $localTable. </div>
        <div><b>($skipped)</b> records were skipped</div>
        ";
    }
    /**
     * Function description
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @return void
     */
    public function updateAndAddNew(){
        $this->onlyUpdate();
        $this->onlyAddNew();
    }
    
    /**
     * Get new records from source
     * 
     * @since 8 мая 2010
     * @author diotheos
     * @return array
     */
    public function getNewRecords($customFilter = false){
        // getting largest integration value
        $maxLocalId = $this->getLocalConnection()
           ->createQuery()
           ->select('MAX('.$this->s2i.')')
           ->from($this->integration)
           ->execute(array(),Doctrine::HYDRATE_SINGLE_SCALAR);   
        $maxLocalId = $maxLocalId?$maxLocalId:0;
        $this->sourceFilter .= $customFilter?$customFilter:" AND ".$this->s." > ".$maxLocalId;
        
        return $this->getFromSource();
    }
    /**
     * Load existed values
     * 
     * @since 9 мая 2010
     * @author diotheos
     * @return array
     */
    public function getExistedRecords($customFilter = false){
    	$localIds = $this->getLocalConnection()
    	   ->createQuery()
    	   ->select($this->s2i)
    	   ->from($this->integration)
    	   ->execute()
    	   ->toKeyValueArray($this->d2i,$this->s2i);
    	$ids2load = array_values($localIds);
    	if(count($ids2load)){
    		$this->sourceFilter .= $customFilter?$customFilter:" AND " . $this->s . " IN ( " . implode(',',$ids2load) . " ) ";
    	}
    	return $this->getFromSource();
    }
    
    /**
     * Gets linked record from local database
     * 
     * @since 9 мая 2010
     * @author diotheos
     * @return Doctrine_Record
     */
    public function getLinkedRecord($remoteId){
    	$localRecord = $this->getLocalConnection()
    	   ->createQuery()
    	   ->select("d.*")
    	   ->from($this->destination . " d")
    	   ->where('d.'.$this->d ." IN ( SELECT i." .$this->d2i. " FROM " .$this->integration. " i WHERE i.".$this->s2i." = ". $remoteId ." )")
    	   ->limit(1);
    	return $localRecord->fetchOne();
    }
    
    /**
     * Function description
     * 
     * @since 9 мая 2010
     * @author diotheos
     * @return Doctrine_Record
     */
    public function createLinkedRecord(){
    	$localRecord = new $this->destination;
    	return $localRecord;  	   
    }
    /**
     * Writes integration bridge between source and destination in integration table
     * 
     * @since 9 мая 2010
     * @author diotheos
     * @return void
     */
    public function writeIntegrationBridge($local,$remote){
    	$bridge = $this->integration;
    	$bridge = new $bridge();
    	$localColumn = $this->d2i;
    	$remoteColumn = $this->s2i;
    	$bridge->$localColumn = $local;
    	$bridge->$remoteColumn = $remote;
    	$bridge->save();
    }
    /**
     * Function description
     * 
     * @since 9 мая 2010
     * @author diotheos
     * @return void
     */
    public function getConfigSection($section){
    	$sectionConfig = $this->_config->$section->toArray();
    	$sectionConfig['integrationMode'] = isset($sectionConfig['mode'])?$sectionConfig['mode']: (self::MODE_ONLY_ADD_NEW);
        
        $sectionConfig['integration'] = isset($sectionConfig['integration'])?$sectionConfig['integration']:"Integrate".$sectionConfig['destination'];
        
        $sectionConfig['s2i'] = isset($sectionConfig['s2i'])?$sectionConfig['s2i']:'old_'.$sectionConfig['d2i'];
    	return $sectionConfig;
    }
}